{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.204710Z",
     "start_time": "2025-01-17T07:01:03.200242Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.315487Z",
     "start_time": "2025-01-17T07:01:03.253556Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing()\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.319052Z",
     "start_time": "2025-01-17T07:01:03.315487Z"
    }
   },
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 5
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.351663Z",
     "start_time": "2025-01-17T07:01:03.319052Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.358966Z",
     "start_time": "2025-01-17T07:01:03.352666Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 7
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.365716Z",
     "start_time": "2025-01-17T07:01:03.358966Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 8
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.370910Z",
     "start_time": "2025-01-17T07:01:03.365716Z"
    }
   },
   "source": [
    "train_ds[1]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 9
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.373601Z",
     "start_time": "2025-01-17T07:01:03.370910Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 16\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 10
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.376508Z",
     "start_time": "2025-01-17T07:01:03.373601Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 1)\n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.379993Z",
     "start_time": "2025-01-17T07:01:03.376508Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 12
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.383423Z",
     "start_time": "2025-01-17T07:01:03.380998Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 13
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.386348Z",
     "start_time": "2025-01-17T07:01:03.383423Z"
    }
   },
   "source": [
    "# 自定义MSEloss\n",
    "\n",
    "def mse_loss(array1, array2):\n",
    "    return ((array1 - array2) ** 2).mean()"
   ],
   "outputs": [],
   "execution_count": 14
  },
  {
   "cell_type": "code",
   "source": [
    "\n",
    "# 假设 y_true 是真实值的张量，y_pred 是模型预测值的张量\n",
    "y_true = torch.tensor([3, -0.5, 2, 7])\n",
    "y_pred = torch.tensor([2.5, 0.0, 2.0, 8])\n",
    "\n",
    "# 计算 MSE 损失\n",
    "loss = F.mse_loss(y_pred, y_true)\n",
    "loss"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.390857Z",
     "start_time": "2025-01-17T07:01:03.386348Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(0.3750)"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 15
  },
  {
   "cell_type": "code",
   "source": [
    "mse_loss(y_pred, y_true)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:03.397614Z",
     "start_time": "2025-01-17T07:01:03.390857Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(0.3750)"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 16
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:25.144939Z",
     "start_time": "2025-01-17T07:01:03.398125Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用自定义的MSE\n",
    "loss_fct = mse_loss\n",
    "# loss_fct = F.mse_loss\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/72600 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "5444a91626154c4eb8b9d75c4307ef61"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    }
   ],
   "execution_count": 17
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:25.206534Z",
     "start_time": "2025-01-17T07:01:25.144939Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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RdeuXVV1c/PmzTFp0iSYyfz583HFFVeornMyhtOmTSv2damDHjduHOrWrYu4uDgMGDAA27dvL3afU6dOYdSoUaoRTmpqKm6//XacPXu22H3WrVuHiy++WD2WpSPfiy++WOpYvvvuO/U3lPt06NAB06dPh5nHdvTo0aUew4MHDy52H45tcc8//zx69OihuhzLc3b48OHYunVrsfsE8rlvptdob8a2b9++pR6zd999d3iMrWYC33zzjRYdHa19/PHH2saNG7U77rhDS01N1Y4ePRrsQwsJTz75pNauXTvt8OHDrtvx48ddX7/77ru1hg0barNnz9ZWrlypnX/++doFF1zg+npBQYHWvn17bcCAAdrq1au16dOna2lpadpjjz3mus+uXbu0+Ph4bezYsdqmTZu0N998U4uIiNBmzJihmYX83o8//rg2ZcoUWUmlTZ06tdjXX3jhBS0lJUWbNm2atnbtWu3KK6/UmjZtquXk5LjuM3jwYK1Tp07a0qVLtQULFmjNmzfXbrzxRtfX09PTtdq1a2ujRo3SNmzYoH399ddaXFyc9t5777nus2jRIjW2L774ohrrJ554QouKitLWr1+vmXVsb731VjV27o/hU6dOFbsPx7a4Sy+9VPvkk0/U77pmzRpt6NChWqNGjbSzZ88G/Llvttdob8b2L3/5i/o93R+z8hgMh7E1RWDRs2dP7b777nN9bLfbtXr16mnPP/98UI8rlAILecH15MyZM+qF87vvvnN9bvPmzerFfcmSJepjecBbrVbtyJEjrvu88847WnJyspaXl6c+/uc//6mCF3fXX3+9egKaUcmTX2FhoVanTh3tpZdeKja2MTEx6gQm5IVB/t+KFStc9/n11181i8WiHTx4UH389ttva9WqVXONq3j00Ue1Vq1auT6+7rrrtMsuu6zY8fTq1Uu76667NDMoK7AYNmxYmf+HY1uxY8eOqTGaN29ewJ/7Zn+NLjm2emDx4IMPamUx89hW+amQ/Px8rFq1SqWd3fcjkY+XLFkS1GMLJZKSlzRzs2bNVLpYUnBCxk627XUfP0kDN2rUyDV+8lZSwrVr13bd59JLL1Wb52zcuNF1H/fvod8nXP4Gu3fvxpEjR4qNgfTUl7Sk+zhKir579+6u+8j95fG6bNky13369OmD6OjoYuMoadbTp0+H9VhLSljSxa1atcI999yD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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 18
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:01:25.234597Z",
     "start_time": "2025-01-17T07:01:25.206534Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3314\n"
     ]
    }
   ],
   "execution_count": 19
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
